package org.hackystat.ambientdevice.device.bunny;

import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
import org.hackystat.ambientdevice.ConfigurationException;
import org.hackystat.ambientdevice.device.AmbientAction;
import org.hackystat.ambientdevice.device.AmbientDevice;
import org.hackystat.ambientdevice.server.jaxb.Action;
import org.restlet.Client;
import org.restlet.data.Method;
import org.restlet.data.Protocol;
import org.restlet.data.Request;
import org.restlet.data.Response;

/**
 * Class that represents the Nabaztag.
 * 
 * @author Sonwright M. Gomez
 * 
 */
public class Nabaztag extends AmbientDevice {
  /** serialVersionUID. */
  private static final long serialVersionUID = 6700463869208547560L;
  /** A map that stores Nabaztag isntances with their device ID. */
  private static final Map<String, Nabaztag> BUNNY_LIST = new HashMap<String, Nabaztag>();
  /** {@literal} SERVER the default Ambient service URL. */
  public static final String SERVER = "http://api.nabaztag.com/vl/FR/api.jsp";
  /** {@literal} QUERYMARK indicator. */
  public static final String QUERYMARK = "?";
  /** {@literal} AMPERSAND indicator. */
  public static final String AMPERSAND = "&";
  /** Logger. * */
  private Logger logger = Logger.getLogger("org.hackystat.ambientdevice");

  // Required parameters to call Nabaztag
  /** Serial Number of Nabaztag. */
  private final String serialNumber; // required.
  /** Token Number of Nabaztag. */
  private final String token; // required.

  // Optional actions of Nabaztag
  /** Left ear position of Nabaztag. */
  private EarPosition leftPos; // optional.
  /** Right Ear position of Nabaztag. */
  private EarPosition rightPos; // optional.
  /** text to speech. */
  private String textToSpeech;

  /**
   * Enumeration class of ear position for Ambient Bunny. The value of 0 denotes that the ear be at
   * a vertical position and the value of 16 denotes that the ear be at a horizontal position. The
   * values are required when using it for Violet API.
   * 
   * @author Sonwright M. Gomez
   * 
   */
  public enum EarPosition {
    /** 0. */
    ZERO, /** 1. */
    ONE, /** 2. */
    TWO,
    /** 3. */
    THREE, /** 4. */
    FOUR, /** 5. */
    FIVE, /** 6. */
    SIX,
    /** 7. */
    SEVEN, /** 8. */
    EIGHT, /** 9. */
    NINE, /** 10. */
    TEN,
    /** 11. */
    ELEVEN, /** 12. */
    TWELVE, /** 13. */
    THIRTEEN, /** 14. */
    FOURTEEN,
    /** 15. */
    FIFTEEN, /** 16. */
    SIXTEEN, /** invalid. */
    Unsupported;
    /**
     * Returns a DEVICE enumeration corresponding to the string. If no corresponding enumeration is
     * found, Unsupported will be returned.
     * 
     * @param string the string.
     * @return enumeration of the DEVICE.
     */
    public static EarPosition getEnumValue(String string) {
      try {
        return EarPosition.valueOf(string);
      }
      catch (IllegalArgumentException e) {
        return Unsupported;
      }
    }
  }

  /** Enumeration class of Nabaztag's action. */
  enum NABAZTAG_ACTION {
    TTS, ChangeLeftEarPosition, ChangeRightEarPosition, Unsupported;
    /**
     * Return a NABAZTAG_ACTION enumeration corresponding to the string. If no corresponding
     * enumeration is found, Unsupported will be returned.
     * 
     * @param string the string
     * @return enumeration of NABAZTAG_ACTION
     */
    public static NABAZTAG_ACTION getEnumValue(String string) {
      try {
        return NABAZTAG_ACTION.valueOf(string);
      }
      catch (IllegalArgumentException e) {
        return Unsupported;
      }
    }
  };

  /**
   * Private constructor for AmbientBunny object. On call, it will say a little message.
   * 
   * @param serialNumber the serial number, or mac address of the bunny.
   * @param token a token given by Violet, that is required to access the bunny.
   */
  private Nabaztag(String serialNumber, String token) {
    super();
    this.serialNumber = serialNumber;
    this.token = token; // won't be hardcoded once XML schema has tokens.
    this.textToSpeech = "Hacky Stat For the Win!";
  }

  /**
   * Returns the AmbientBunny object, with the given serial number and token.
   * 
   * @param serialNumber the serial number, or mac address of the bunny.
   * @param token a token given by Violet, that is required to access the bunny.
   * @return Ambient bunny with the given SN.
   */
  public static Nabaztag getBunny(String serialNumber, String token) {
    Nabaztag bunny = BUNNY_LIST.get(serialNumber);
    if (bunny == null) {
      bunny = new Nabaztag(serialNumber, token);
      BUNNY_LIST.put(serialNumber, bunny);
    }
    return bunny;
  }

  /**
   * Update the AmbientBunny's state by sending HTTP request to the AmbientBunny control server.
   * 
   * @return respone state from the control server.
   */
  public String updateTextToSpeechState() {
    // Prepare the request
    Request request = new Request(Method.POST, this.getTextToSpeechUrl());
    request.setReferrerRef("http://www.hackystat.org");

    // Handle it using an HTTP client connector
    Client client = new Client(Protocol.HTTP);
    Response response = client.handle(request);

    if (response.getStatus().isSuccess()) {
      return Boolean.TRUE.toString();
    }
    else {
      return response.getStatus().getDescription();
    }
  }

  /**
   * Update's the Nabaztag's Ear position state by sending HTTP request to the bunny.
   * 
   * @return String the status of the right ear?
   */
  public String updateLeftEarState() {
    Request request = new Request(Method.POST, this.getLeftEarUrl());
    request.setReferrerRef("http://www.hackystat.org");

    // Handle it using an HTTP client connector
    Client client = new Client(Protocol.HTTP);
    Response response = client.handle(request);

    if (response.getStatus().isSuccess()) {
      return Boolean.TRUE.toString();
    }
    else {
      return response.getStatus().getDescription();
    }
  }

  /**
   * Update's the nabaztag's right ear position state by sending HTTP request to the device.
   * 
   * @return the
   */
  public String updateRightEarState() {
    Request request = new Request(Method.POST, this.getRightEarUrl());
    request.setReferrerRef("http://www.hackystat.org");

    // Handle it using an HTTP client connector
    Client client = new Client(Protocol.HTTP);
    Response response = client.handle(request);

    if (response.getStatus().isSuccess()) {
      return Boolean.TRUE.toString();
    }
    else {
      return response.getStatus().getDescription();
    }
  }

  /**
   * Send out text to speech command to bunny.
   * 
   * @return the query portion of the URL.
   */
  private String getTextToSpeechQuery() {
    String text = this.textToSpeech.trim().replace("Hackystat", "hacky stat").replace(" ", "+");
    return QUERYMARK + "sn=" + this.serialNumber + AMPERSAND + "token=" + this.token + AMPERSAND
        + "tts=" + text;
  }

  /**
   * Sends out ear position command to the bunny. The position given is opposite because the
   * Nabaztag API is mirrored with respect to the person looking at the bunny.
   * 
   * @return String the query for the left ear.
   */
  private String getLeftEarPositionQuery() {
    logger.info("Left Ear Position at: " + this.leftPos);
    return QUERYMARK + "sn=" + this.serialNumber + AMPERSAND + "token=" + this.token + AMPERSAND
        + "posright=" + this.leftPos.ordinal();
  }

  /**
   * Sends out ear position command to the bunny. The position given is opposite because the
   * Nabaztag API is backwards.
   * 
   * @return String the query for the right ear.
   */
  private String getRightEarPositionQuery() {
    logger.info("Right Ear Position at: " + this.rightPos);
    return QUERYMARK + "sn=" + this.serialNumber + AMPERSAND + "token=" + this.token + AMPERSAND
        + "posleft=" + this.rightPos.ordinal();
  }

  /**
   * Get the URL of the left ear.
   * 
   * @return String the position of the left ear.
   */
  public String getLeftEarUrl() {
    return SERVER + getLeftEarPositionQuery();
  }

  /**
   * Get the URL of the right ear.
   * 
   * @return String the position of the right ear.
   */
  public String getRightEarUrl() {
    return SERVER + getRightEarPositionQuery();
  }

  /**
   * @return the URL that sets the bunny state.
   */
  public String getTextToSpeechUrl() {
    return SERVER + getTextToSpeechQuery();
  }

  /**
   * @return the serialNumber.
   */
  public String getSerialNumber() {
    return this.serialNumber;
  }

  /**
   * @return the bunny's left ear position.
   */
  public EarPosition getLeftPos() {
    return this.leftPos;
  }

  /**
   * @return the bunny's right ear position.
   */
  public EarPosition getRightPos() {
    return this.rightPos;
  }

  /**
   * @param pos the position of the left ear.
   */
  public void setLeftPos(String pos) {
    this.leftPos = EarPosition.valueOf(pos);
  }

  /**
   * @param pos the position of the left ear.
   */
  public void setLeftPos(EarPosition pos) {
    this.leftPos = pos;
  }

  /**
   * @param pos the position of the right ear.
   */
  public void setRightPos(String pos) {
    this.rightPos = EarPosition.valueOf(pos);
  }

  /**
   * @param pos the position of the right ear.
   */
  public void setRightPos(EarPosition pos) {
    this.rightPos = pos;
  }

  /**
   * @return the text to be synthesized to speech.
   */
  public String geTextToSpeech() {
    return textToSpeech;
  }

  /**
   * @param textToSpeech the text to be synthesized to speech.
   */
  public void setTextToSpeech(String textToSpeech) {
    this.textToSpeech = textToSpeech;
  }

  /**
   * @return the token
   */
  public String getToken() {
    return token;
  }
  
  /**
   * @return Returns text to speech
   */
  public String getTextToSpeech() {
    return this.textToSpeech;
  }

  /**
   * Return an Nabaztag action according to the Action object.
   * 
   * @param action the specified Action object.
   * @param device
   * @return an AmbientAction
   * @throws ConfigurationException for some error
   */
  @Override
  public AmbientAction getAction(Action action) throws ConfigurationException {
    switch (NABAZTAG_ACTION.getEnumValue(action.getActionType())) {
      case TTS:
        return new TextToSpeech(this, action.getValue());
      case ChangeLeftEarPosition:
        return new ChangeLeftEarPosition(this, action.getValue());
      case ChangeRightEarPosition:
        return new ChangeRightEarPosition(this, action.getValue());
      default:
        throw new ConfigurationException("Action " + action.getActionType() + " is not supported"
          + " by the Nabaztag.");
    }
  }
}
